#include "simulationTime.hpp"
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>

using Catch::Approx;
using namespace hms;

TEST_CASE( "Simulation time: Construction and setting parameters" ){
	scalar start {0};
	scalar end {10};
	scalar maxStep {2};
	scalar timeInterval {1};
	long int stepInterval {1};

	auto test = [&]( const Time& time, bool writeAtTimeIntervals ){

		REQUIRE( time.current() == start );
		REQUIRE( time.end() == end );
		REQUIRE( time.maxTimeStep() == maxStep );
		REQUIRE( time.nSteps() == 0 );

		REQUIRE( time.writeAtTimeIntervals() == writeAtTimeIntervals );
		if ( writeAtTimeIntervals )
			REQUIRE( time.writeTimeInterval() == timeInterval );
		else
			REQUIRE( time.writeStepInterval() == stepInterval );
	};

	SECTION( "Default construction" ){
		Time time;
		time.startAt(start);
		time.endAt(end);
		time.maxTimeStep(maxStep);

		time.writeAtTimeIntervals(true);
		time.writeTimeInterval(timeInterval);
		test(time, true);

		time.writeAtTimeIntervals( false );
		time.writeStepInterval(stepInterval);
		test(time, false);
	}
	SECTION( "Minimal initialisation" ){
		Time time { end, timeInterval };
		time.maxTimeStep(maxStep);
		test(time, true);
	}
	SECTION( "Full initialisation" ){
		test( Time{start, end, maxStep, timeInterval}, true );
		test( Time{start, end, maxStep, stepInterval}, false );
	}
}

TEST_CASE( "Simulation time: advancing" ){
	scalar start = 0, end = 7, maxStep = 2.0, writeInterval = 3.0;
	Time time {start, end, maxStep, writeInterval};
	SECTION( "manually" ){
		REQUIRE( time.current() == 0 );
		REQUIRE( time.nSteps() == 0 );
		time.advanceBy(0.1);
		REQUIRE( time.step() == 0.1 );
		REQUIRE( time.nSteps() == 1 );
		/* requesting too large a time step */
		time.advanceBy(2.1);
		REQUIRE( time.step() == maxStep );
		REQUIRE( time.nSteps() == 2 );
		REQUIRE( ( !time.atEnd() && !time.atWriteTime() ) );
		REQUIRE( time.current() == Approx(2.1) );
		REQUIRE( time.nWritten() == 0 );
		/* hitting the write interval */
		time.step(1.0);
		REQUIRE( time.step() == Approx(0.9) );
		time.advance();
		REQUIRE( time.current() == Approx(writeInterval) );
		REQUIRE( time.atWriteTime() );
		REQUIRE( time.nWritten() == 1 );

		time.advanceBy(maxStep);
		REQUIRE( time.step() == maxStep );
		REQUIRE( time.current() == Approx(5.0) );
		time.step(1.1);
		REQUIRE( time.step() == Approx(1.0) );
		time.advance();
		REQUIRE( time.current() == Approx(2*writeInterval) );
		REQUIRE( time.atWriteTime() );
		REQUIRE( time.nWritten() == 2 );
		/* overshooting the simulation end time */
		time.step(maxStep);
		REQUIRE( time.step() == Approx(1.0) );
		time.advance();
		REQUIRE( time.atEnd() );
		/* output must be written when reaching the end */
		REQUIRE( time.atWriteTime() );
		REQUIRE( time.nWritten() == 3 );
	}
	SECTION( "in a loop" ){
		int writeCounter {0};
		while ( !time.atEnd() ){
			time.advanceBy(maxStep);
			scalar step { time.step() };
			REQUIRE(
				( step == maxStep || step == Approx(writeInterval-maxStep) )
			);
			if ( time.atWriteTime() )
				++writeCounter;
			REQUIRE( time.nWritten() == writeCounter );
		}
		REQUIRE( writeCounter == 3 );
	}
}
